/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2014 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.warp;

import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.util.Map;
import org.eclipse.imagen.ImageLayout;
import org.eclipse.imagen.Interpolation;
import org.eclipse.imagen.PlanarImage;
import org.eclipse.imagen.ROI;
import org.eclipse.imagen.RasterAccessor;
import org.eclipse.imagen.Warp;
import org.eclipse.imagen.iterator.RandomIter;
import org.eclipse.imagen.media.iterators.RandomIterFactory;
import org.eclipse.imagen.media.range.Range;

/**
 * An <code>OpImage</code> implementing the general "Warp" operation as described in <code>
 * org.eclipse.imagen.operator.WarpDescriptor</code>. It supports the nearest-neighbor interpolation.
 *
 * <p>The layout for the destination image may be specified via the <code>ImageLayout</code> parameter. However, only
 * those settings suitable for this operation will be used. The unsuitable settings will be replaced by default suitable
 * values. An optional ROI object and a NoData Range can be used. If a backward mapped pixel lies outside ROI or it is a
 * NoData, then the destination pixel value is a background value.
 *
 * @since EA2
 * @see org.eclipse.imagen.Warp
 * @see org.eclipse.imagen.WarpOpImage
 * @see org.eclipse.imagen.operator.WarpDescriptor
 * @see WarpRIF
 */
@SuppressWarnings("unchecked")
final class WarpNearestOpImage extends WarpOpImage {
    /** LookupTable used for a faster NoData check */
    private byte[][] byteLookupTable;

    /**
     * Constructs a WarpNearestOpImage.
     *
     * @param source The source image.
     * @param config RenderingHints used in calculations.
     * @param layout The destination image layout.
     * @param warp An object defining the warp algorithm.
     * @param interp An object describing the interpolation method.
     * @param roi input ROI object used.
     * @param noData NoData Range object used for checking if NoData are present.
     */
    public WarpNearestOpImage(
            final RenderedImage source,
            final Map<?, ?> config,
            final ImageLayout layout,
            final Warp warp,
            final Interpolation interp,
            final ROI sourceROI,
            Range noData,
            double[] bkg) {
        super(
                source, layout, config, false, null, // extender not needed in nearest-neighbor interpolation
                interp, warp, bkg, sourceROI, noData);

        /*
         * If the source has IndexColorModel, override the default setting in OpImage. The dest shall have exactly the same SampleModel and ColorModel
         * as the source. Note, in this case, the source should have an integral data type.
         */
        final ColorModel srcColorModel = source.getColorModel();
        if (srcColorModel instanceof IndexColorModel) {
            sampleModel = source.getSampleModel().createCompatibleSampleModel(tileWidth, tileHeight);
            colorModel = srcColorModel;
        }

        /*
         * Selection of a destinationNoData value for each datatype
         */
        SampleModel sm = source.getSampleModel();
        // Source image data Type
        int srcDataType = sm.getDataType();

        // Creation of a lookuptable containing the values to use for no data
        if (srcDataType == DataBuffer.TYPE_BYTE && hasNoData) {
            int numBands = getNumBands();
            byteLookupTable = new byte[numBands][256];
            for (int b = 0; b < numBands; b++) {
                for (int i = 0; i < byteLookupTable[0].length; i++) {
                    byte value = (byte) i;
                    if (noDataRange.contains(value)) {
                        byteLookupTable[b][i] = (byte) backgroundValues[b];
                    } else {
                        byteLookupTable[b][i] = value;
                    }
                }
            }
        }
    }

    protected void computeRectByte(
            final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile) {
        // Random Iterator on the source image bounds
        final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
        // Initial settings
        final int minX = src.getMinX();
        final int maxX = src.getMaxX();
        final int minY = src.getMinY();
        final int maxY = src.getMaxY();

        final int dstWidth = dst.getWidth();
        final int dstHeight = dst.getHeight();
        final int dstBands = dst.getNumBands();

        final int lineStride = dst.getScanlineStride();
        final int pixelStride = dst.getPixelStride();
        final int[] bandOffsets = dst.getBandOffsets();
        final byte[][] data = dst.getByteDataArrays();

        final float[] warpData = new float[2 * dstWidth];

        int lineOffset = 0;

        // NO ROI AND NODATA
        if (caseA || (caseB && roiContainsTile)) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);
                    // If the pixel is outside the input image bounds
                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
                            }
                        }
                    } else {
                        // Nearest interpolation
                        for (int b = 0; b < dstBands; b++) {
                            data[b][pixelOffset + bandOffsets[b]] = (byte) (iter.getSample(sx, sy, b) & 0xFF);
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY ROI
        } else if (caseB) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
                                }
                            }
                        } else {
                            // Else the related source pixel is set
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (byte) (iter.getSample(sx, sy, b) & 0xFF);
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY NODATA
        } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
                            }
                        }
                    } else {
                        // The related source pixel is set if it isn't a nodata
                        for (int b = 0; b < dstBands; b++) {
                            data[b][pixelOffset + bandOffsets[b]] = byteLookupTable[b][iter.getSample(sx, sy, b)];
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // BOTH ROI AND NODATA
        } else {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy)
                                && roiBounds.contains(sx, sy)
                                && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (byte) backgroundValues[b];
                                }
                            }
                        } else {
                            // The related source pixel is set if it isn't a nodata
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = byteLookupTable[b][iter.getSample(sx, sy, b)];
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
        }
        iter.done();
    }

    protected void computeRectUShort(
            final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile) {
        // Random Iterator on the source image bounds
        final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
        // Initial settings
        final int minX = src.getMinX();
        final int maxX = src.getMaxX();
        final int minY = src.getMinY();
        final int maxY = src.getMaxY();

        final int dstWidth = dst.getWidth();
        final int dstHeight = dst.getHeight();
        final int dstBands = dst.getNumBands();

        final int lineStride = dst.getScanlineStride();
        final int pixelStride = dst.getPixelStride();
        final int[] bandOffsets = dst.getBandOffsets();
        final short[][] data = dst.getShortDataArrays();

        final float[] warpData = new float[2 * dstWidth];

        int lineOffset = 0;

        // NO ROI AND NODATA
        if (caseA || (caseB && roiContainsTile)) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);
                    // If the pixel is outside the input image bounds
                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            }
                        }
                    } else {
                        // Nearest interpolation
                        for (int b = 0; b < dstBands; b++) {
                            data[b][pixelOffset + bandOffsets[b]] = (short) (iter.getSample(sx, sy, b) & 0xFFFF);
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY ROI
        } else if (caseB) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy)
                                && roiBounds.contains(sx, sy)
                                && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                                }
                            }
                        } else {
                            // Else the related source pixel is set
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) (iter.getSample(sx, sy, b) & 0xFFFF);
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY NODATA
        } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
            short inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            }
                        }
                    } else {
                        // The related source pixel is set if it isn't a nodata
                        for (int b = 0; b < dstBands; b++) {
                            // Input value selected
                            inputValue = (short) (iter.getSample(sx, sy, b) & 0xFFFF);
                            if (noDataRange.contains(inputValue)) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            } else {
                                data[b][pixelOffset + bandOffsets[b]] = inputValue;
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // BOTH ROI AND NODATA
        } else {
            short inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                                }
                            }
                        } else {
                            // The related source pixel is set if it isn't a nodata
                            for (int b = 0; b < dstBands; b++) {
                                // Input value selected
                                inputValue = (short) (iter.getSample(sx, sy, b) & 0xFFFF);
                                if (noDataRange.contains(inputValue)) {
                                    data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                                } else {
                                    data[b][pixelOffset + bandOffsets[b]] = inputValue;
                                }
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
        }
        iter.done();
    }

    protected void computeRectShort(
            final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile) {
        // Random Iterator on the source image bounds
        final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
        // Initial settings
        final int minX = src.getMinX();
        final int maxX = src.getMaxX();
        final int minY = src.getMinY();
        final int maxY = src.getMaxY();

        final int dstWidth = dst.getWidth();
        final int dstHeight = dst.getHeight();
        final int dstBands = dst.getNumBands();

        final int lineStride = dst.getScanlineStride();
        final int pixelStride = dst.getPixelStride();
        final int[] bandOffsets = dst.getBandOffsets();
        final short[][] data = dst.getShortDataArrays();

        final float[] warpData = new float[2 * dstWidth];

        int lineOffset = 0;

        // NO ROI AND NODATA
        if (caseA || (caseB && roiContainsTile)) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);
                    // If the pixel is outside the input image bounds
                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            }
                        }
                    } else {
                        // Nearest interpolation
                        for (int b = 0; b < dstBands; b++) {
                            data[b][pixelOffset + bandOffsets[b]] = (short) iter.getSample(sx, sy, b);
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY ROI
        } else if (caseB) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                                }
                            }
                        } else {
                            // Else the related source pixel is set
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) iter.getSample(sx, sy, b);
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY NODATA
        } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
            short inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            }
                        }
                    } else {
                        // The related source pixel is set if it isn't a nodata
                        for (int b = 0; b < dstBands; b++) {
                            // Input value selected
                            inputValue = (short) iter.getSample(sx, sy, b);
                            if (noDataRange.contains(inputValue)) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            } else {
                                data[b][pixelOffset + bandOffsets[b]] = inputValue;
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // BOTH ROI AND NODATA
        } else {
            short inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                                }
                            }
                        } else {
                            // The related source pixel is set if it isn't a nodata
                            for (int b = 0; b < dstBands; b++) {
                                // Input value selected
                                inputValue = (short) iter.getSample(sx, sy, b);
                                if (noDataRange.contains(inputValue)) {
                                    data[b][pixelOffset + bandOffsets[b]] = (short) backgroundValues[b];
                                } else {
                                    data[b][pixelOffset + bandOffsets[b]] = inputValue;
                                }
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
        }
        iter.done();
    }

    protected void computeRectInt(
            final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile) {
        // Random Iterator on the source image bounds
        final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
        // Initial settings
        final int minX = src.getMinX();
        final int maxX = src.getMaxX();
        final int minY = src.getMinY();
        final int maxY = src.getMaxY();

        final int dstWidth = dst.getWidth();
        final int dstHeight = dst.getHeight();
        final int dstBands = dst.getNumBands();

        final int lineStride = dst.getScanlineStride();
        final int pixelStride = dst.getPixelStride();
        final int[] bandOffsets = dst.getBandOffsets();
        final int[][] data = dst.getIntDataArrays();

        final float[] warpData = new float[2 * dstWidth];

        int lineOffset = 0;

        // NO ROI AND NODATA
        if (caseA || (caseB && roiContainsTile)) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);
                    // If the pixel is outside the input image bounds
                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                            }
                        }
                    } else {
                        // Nearest interpolation
                        for (int b = 0; b < dstBands; b++) {
                            data[b][pixelOffset + bandOffsets[b]] = iter.getSample(sx, sy, b);
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY ROI
        } else if (caseB) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                                }
                            }
                        } else {
                            // Else the related source pixel is set
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = iter.getSample(sx, sy, b);
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY NODATA
        } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
            int inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                            }
                        }
                    } else {
                        // The related source pixel is set if it isn't a nodata
                        for (int b = 0; b < dstBands; b++) {
                            // Input value selected
                            inputValue = iter.getSample(sx, sy, b);
                            if (noDataRange.contains(inputValue)) {
                                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                            } else {
                                data[b][pixelOffset + bandOffsets[b]] = inputValue;
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // BOTH ROI AND NODATA
        } else {
            int inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                                }
                            }
                        } else {
                            // The related source pixel is set if it isn't a nodata
                            for (int b = 0; b < dstBands; b++) {
                                // Input value selected
                                inputValue = iter.getSample(sx, sy, b);
                                if (noDataRange.contains(inputValue)) {
                                    data[b][pixelOffset + bandOffsets[b]] = (int) backgroundValues[b];
                                } else {
                                    data[b][pixelOffset + bandOffsets[b]] = inputValue;
                                }
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
        }
        iter.done();
    }

    protected void computeRectFloat(
            final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile) {
        // Random Iterator on the source image bounds
        final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
        // Initial settings
        final int minX = src.getMinX();
        final int maxX = src.getMaxX();
        final int minY = src.getMinY();
        final int maxY = src.getMaxY();

        final int dstWidth = dst.getWidth();
        final int dstHeight = dst.getHeight();
        final int dstBands = dst.getNumBands();

        final int lineStride = dst.getScanlineStride();
        final int pixelStride = dst.getPixelStride();
        final int[] bandOffsets = dst.getBandOffsets();
        final float[][] data = dst.getFloatDataArrays();

        final float[] warpData = new float[2 * dstWidth];

        int lineOffset = 0;

        // NO ROI AND NODATA
        if (caseA || (caseB && roiContainsTile)) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);
                    // If the pixel is outside the input image bounds
                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                            }
                        }
                    } else {
                        // Nearest interpolation
                        for (int b = 0; b < dstBands; b++) {
                            data[b][pixelOffset + bandOffsets[b]] = iter.getSampleFloat(sx, sy, b);
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY ROI
        } else if (caseB) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                                }
                            }
                        } else {
                            // Else the related source pixel is set
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = iter.getSampleFloat(sx, sy, b);
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY NODATA
        } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
            float inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                            }
                        }
                    } else {
                        // The related source pixel is set if it isn't a nodata
                        for (int b = 0; b < dstBands; b++) {
                            // Input value selected
                            inputValue = iter.getSampleFloat(sx, sy, b);
                            if (noDataRange.contains(inputValue)) {
                                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                            } else {
                                data[b][pixelOffset + bandOffsets[b]] = inputValue;
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // BOTH ROI AND NODATA
        } else {
            float inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                                }
                            }
                        } else {
                            // The related source pixel is set if it isn't a nodata
                            for (int b = 0; b < dstBands; b++) {
                                // Input value selected
                                inputValue = iter.getSampleFloat(sx, sy, b);
                                if (noDataRange.contains(inputValue)) {
                                    data[b][pixelOffset + bandOffsets[b]] = (float) backgroundValues[b];
                                } else {
                                    data[b][pixelOffset + bandOffsets[b]] = inputValue;
                                }
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
        }
        iter.done();
    }

    protected void computeRectDouble(
            final PlanarImage src, final RasterAccessor dst, final RandomIter roiIter, boolean roiContainsTile) {
        // Random Iterator on the source image bounds
        final RandomIter iter = RandomIterFactory.create(src, src.getBounds(), TILE_CACHED, ARRAY_CALC);
        // Initial settings
        final int minX = src.getMinX();
        final int maxX = src.getMaxX();
        final int minY = src.getMinY();
        final int maxY = src.getMaxY();

        final int dstWidth = dst.getWidth();
        final int dstHeight = dst.getHeight();
        final int dstBands = dst.getNumBands();

        final int lineStride = dst.getScanlineStride();
        final int pixelStride = dst.getPixelStride();
        final int[] bandOffsets = dst.getBandOffsets();
        final double[][] data = dst.getDoubleDataArrays();

        final float[] warpData = new float[2 * dstWidth];

        int lineOffset = 0;

        // NO ROI AND NODATA
        if (caseA || (caseB && roiContainsTile)) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);
                    // If the pixel is outside the input image bounds
                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                            }
                        }
                    } else {
                        // Nearest interpolation
                        for (int b = 0; b < dstBands; b++) {
                            data[b][pixelOffset + bandOffsets[b]] = iter.getSampleDouble(sx, sy, b);
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY ROI
        } else if (caseB) {
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                                }
                            }
                        } else {
                            // Else the related source pixel is set
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = iter.getSampleDouble(sx, sy, b);
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // ONLY NODATA
        } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
            double inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                            }
                        }
                    } else {
                        // The related source pixel is set if it isn't a nodata
                        for (int b = 0; b < dstBands; b++) {
                            // Input value selected
                            inputValue = iter.getSampleDouble(sx, sy, b);
                            if (noDataRange.contains(inputValue)) {
                                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                            } else {
                                data[b][pixelOffset + bandOffsets[b]] = inputValue;
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
            // BOTH ROI AND NODATA
        } else {
            double inputValue = 0;
            for (int h = 0; h < dstHeight; h++) {
                int pixelOffset = lineOffset;
                lineOffset += lineStride;
                // Calculation of the warp for the selected row
                warp.warpRect(dst.getX(), dst.getY() + h, dstWidth, 1, warpData);
                int count = 0;
                for (int w = 0; w < dstWidth; w++) {
                    /*
                     * The warp object subtract 0.5 from backward mapped source coordinate. Need to do a round to get the nearest neighbor. This is
                     * different from the standard nearest implementation.
                     */
                    final int sx = round(warpData[count++]);
                    final int sy = round(warpData[count++]);

                    if (sx < minX || sx >= maxX || sy < minY || sy >= maxY) {
                        /* Fill with a background color. */
                        if (setBackground) {
                            for (int b = 0; b < dstBands; b++) {
                                data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                            }
                        }
                    } else {
                        // SG if we falls outside the roi we use the background value
                        if (!(roiBounds.contains(sx, sy) && roiIter.getSample(sx, sy, 0) > 0)) {
                            /* Fill with a background color. */
                            if (setBackground) {
                                for (int b = 0; b < dstBands; b++) {
                                    data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                                }
                            }
                        } else {
                            // The related source pixel is set if it isn't a nodata
                            for (int b = 0; b < dstBands; b++) {
                                // Input value selected
                                inputValue = iter.getSampleDouble(sx, sy, b);
                                if (noDataRange.contains(inputValue)) {
                                    data[b][pixelOffset + bandOffsets[b]] = backgroundValues[b];
                                } else {
                                    data[b][pixelOffset + bandOffsets[b]] = inputValue;
                                }
                            }
                        }
                    }
                    pixelOffset += pixelStride;
                }
            }
        }
        iter.done();
    }
}
